#!/usr/bin/env python3
"""
RAGEN项目主测试程序
提供统一的测试执行入口，支持分类测试、并行执行和详细报告
"""

import os
import sys
import time
import argparse
import subprocess
import json
from pathlib import Path
from typing import List, Dict, Tuple, Optional

# 添加项目路径
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))


class TestRunner:
    """测试运行器类"""

    def __init__(self, test_dir: Path = None):
        """
        初始化测试运行器

        Args:
            test_dir: 测试目录路径，默认为当前文件所在目录
        """
        self.test_dir = test_dir or Path(__file__).parent
        self.results = {}
        self.project_root = project_root

    def discover_tests(self, pattern: str = "test_*.py") -> Dict[str, List[str]]:
        """
        发现测试文件

        Args:
            pattern: 测试文件匹配模式

        Returns:
            按类别分组的测试文件字典
        """
        test_structure = {
            "unit": [],
            "integration": [],
            "performance": []
        }

        # 遍历测试目录
        for category in test_structure.keys():
            category_dir = self.test_dir / category
            if category_dir.exists():
                for test_file in category_dir.rglob(pattern):
                    if test_file.is_file() and test_file.name.startswith("test_"):
                        # 使用相对路径
                        rel_path = test_file.relative_to(self.project_root)
                        test_structure[category].append(str(rel_path))

        return test_structure

    def run_pytest(self, test_paths: List[str],
                   marker: Optional[str] = None,
                   parallel: bool = False,
                   verbose: bool = True,
                   coverage: bool = False) -> Tuple[int, float]:
        """
        运行pytest测试

        Args:
            test_paths: 测试文件路径列表
            marker: pytest标记（暂不使用，基于路径运行）
            parallel: 是否并行执行
            verbose: 是否详细输出
            coverage: 是否生成覆盖率报告

        Returns:
            (退出码, 执行时间)
        """
        cmd = ["python", "-m", "pytest"]

        # 添加并行参数
        if parallel and len(test_paths) > 1:
            cmd.extend(["-n", "auto"])

        # 添加详细输出
        if verbose:
            cmd.append("-v")

        # 添加覆盖率
        if coverage:
            cmd.extend([
                "--cov=ragen",
                "--cov-report=html",
                "--cov-report=term-missing",
                "--cov-fail-under=80"  # 覆盖率不低于80%
            ])

        # 添加测试路径
        if test_paths:
            cmd.extend(test_paths)
        else:
            cmd.append(str(self.test_dir))

        # 添加其他有用参数
        cmd.extend([
            "--tb=short",  # 简短的错误回溯
            "--disable-warnings",  # 禁用警告
            "--color=yes",  # 彩色输出
        ])

        print(f"\n{'='*60}")
        print(f"🚀 执行命令: {' '.join(cmd)}")
        print(f"{'='*60}\n")

        start_time = time.time()
        result = subprocess.run(cmd, cwd=self.project_root)
        duration = time.time() - start_time

        return result.returncode, duration

    def run_category_tests(self, category: str,
                          parallel: bool = False,
                          coverage: bool = False) -> Dict:
        """
        运行特定类别的测试

        Args:
            category: 测试类别 (unit/integration/performance)
            parallel: 是否并行执行
            coverage: 是否生成覆盖率报告

        Returns:
            测试结果字典
        """
        category_names = {
            "unit": "单元测试",
            "integration": "集成测试",
            "performance": "性能测试"
        }

        print(f"\n🧪 运行 {category_names.get(category, category.upper())} 测试...\n")

        test_structure = self.discover_tests()
        test_paths = test_structure.get(category, [])

        if not test_paths:
            print(f"⚠️  未找到 {category} 类别的测试文件")
            return {"status": "skip", "count": 0, "duration": 0, "returncode": 0}

        # 只对单元测试生成覆盖率报告
        generate_coverage = coverage if category == "unit" else False

        # 运行测试
        returncode, duration = self.run_pytest(
            test_paths,
            marker=None,  # 不使用标记，基于路径运行
            parallel=parallel,
            coverage=generate_coverage
        )

        result = {
            "status": "pass" if returncode == 0 else "fail",
            "count": len(test_paths),
            "duration": duration,
            "returncode": returncode,
            "files": test_paths
        }

        self.results[category] = result
        return result

    def run_all_tests(self,
                     parallel: bool = False,
                     coverage: bool = False,
                     skip_slow: bool = False) -> Dict:
        """
        运行所有测试

        Args:
            parallel: 是否并行执行
            coverage: 是否生成覆盖率报告
            skip_slow: 是否跳过慢速测试

        Returns:
            完整测试结果字典
        """
        print("\n🎯 开始运行 RAGEN 项目完整测试套件\n")
        print("📋 测试执行顺序: 单元测试 → 集成测试 → 性能测试\n")

        start_time = time.time()
        overall_results = {
            "categories": {},
            "total": {
                "tests": 0,
                "passed": 0,
                "failed": 0,
                "skipped": 0,
                "duration": 0,
                "categories_run": []
            }
        }

        # 按顺序运行测试（单元 -> 集成 -> 性能）
        categories = ["unit", "integration", "performance"]

        for category in categories:
            # 跳过慢速测试选项
            if skip_slow and category == "performance":
                print(f"⏭️  跳过 {category} 测试（慢速测试）")
                overall_results["categories"][category] = {
                    "status": "skip", "count": 0, "duration": 0
                }
                continue

            result = self.run_category_tests(category, parallel, coverage)
            overall_results["categories"][category] = result
            overall_results["total"]["tests"] += result["count"]
            overall_results["total"]["duration"] += result["duration"]
            overall_results["total"]["categories_run"].append(category)

            if result["status"] == "pass":
                overall_results["total"]["passed"] += result["count"]
                print(f"✅ {category} 测试通过 ({result['count']} 个文件, {result['duration']:.2f}s)")
            elif result["status"] == "skip":
                overall_results["total"]["skipped"] += result["count"]
                print(f"⏭️  {category} 测试跳过")
            else:
                overall_results["total"]["failed"] += result["count"]
                print(f"❌ {category} 测试失败 ({result['count']} 个文件, {result['duration']:.2f}s)")

        overall_results["total"]["duration"] = time.time() - start_time

        return overall_results

    def print_summary(self, results: Dict):
        """
        打印测试总结报告

        Args:
            results: 测试结果字典

        Returns:
            bool: 所有测试是否通过
        """
        print("\n" + "="*60)
        print("📊 RAGEN 项目测试总结报告")
        print("="*60)

        total = results["total"]

        # 总体统计
        print(f"\n📈 总体统计:")
        print(f"   总测试文件数: {total['tests']}")
        print(f"   通过: {total['passed']} ✅")
        print(f"   失败: {total['failed']} ❌")
        print(f"   跳过: {total['skipped']} ⏭️")
        print(f"   总耗时: {total['duration']:.2f}s")

        # 分类统计
        print(f"\n📋 分类统计:")
        print("-"*40)
        for category, result in results["categories"].items():
            if result["status"] == "pass":
                status = "✅ 通过"
            elif result["status"] == "skip":
                status = "⏭️ 跳过"
            else:
                status = "❌ 失败"

            print(f"   {category:15} {status} {result['count']:3} 文件 ({result['duration']:.2f}s)")

        # 成功率
        if total["tests"] > 0:
            success_rate = (total["passed"] / total["tests"]) * 100
            print(f"\n🎯 测试成功率: {success_rate:.1f}%")

        # 失败提示和结果
        if total["failed"] > 0:
            print(f"\n⚠️  有 {total['failed']} 个测试文件失败，请检查详细日志")
            print("💡 提示: 使用 --verbose 参数查看详细错误信息")
            return False
        else:
            print(f"\n🎉 所有测试通过！RAGEN 项目质量良好")
            return True

    def validate_test_structure(self) -> bool:
        """
        验证测试目录结构

        Returns:
            bool: 结构是否有效
        """
        print("🔍 验证测试目录结构...")

        required_dirs = ["unit", "integration", "performance"]
        missing_dirs = []

        for dir_name in required_dirs:
            dir_path = self.test_dir / dir_name
            if not dir_path.exists() or not dir_path.is_dir():
                missing_dirs.append(dir_name)
            else:
                # 检查是否有__init__.py
                init_file = dir_path / "__init__.py"
                if not init_file.exists():
                    print(f"⚠️  缺少 {dir_name}/__init__.py 文件")

        if missing_dirs:
            print(f"❌ 缺少测试目录: {', '.join(missing_dirs)}")
            return False

        print("✅ 测试目录结构验证通过")
        return True


def parse_arguments():
    """解析命令行参数"""
    parser = argparse.ArgumentParser(
        description="RAGEN项目测试执行器",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
📖 使用示例:
  python run_tests.py                          # 运行所有测试
  python run_tests.py --unit                   # 只运行单元测试
  python run_tests.py --integration            # 只运行集成测试
  python run_tests.py --performance            # 只运行性能测试
  python run_tests.py --parallel               # 并行运行测试
  python run_tests.py --coverage               # 生成覆盖率报告
  python run_tests.py --skip-slow              # 跳过慢速测试
  python run_tests.py --verbose                # 详细输出
  python run_tests.py --list                   # 列出所有测试文件
  python run_tests.py --validate               # 验证测试结构
  python run_tests.py --output results.json    # 保存结果到文件
        """
    )

    # 测试类别选择
    test_group = parser.add_mutually_exclusive_group()
    test_group.add_argument(
        "--unit", action="store_true",
        help="只运行单元测试"
    )
    test_group.add_argument(
        "--integration", action="store_true",
        help="只运行集成测试"
    )
    test_group.add_argument(
        "--performance", action="store_true",
        help="只运行性能测试"
    )

    # 运行选项
    parser.add_argument(
        "--parallel", "-p", action="store_true",
        help="并行运行测试 (需要安装 pytest-xdist)"
    )
    parser.add_argument(
        "--coverage", "-c", action="store_true",
        help="生成代码覆盖率报告 (需要安装 pytest-cov)"
    )
    parser.add_argument(
        "--skip-slow", action="store_true",
        help="跳过慢速测试（性能测试）"
    )
    parser.add_argument(
        "--verbose", "-v", action="store_true",
        help="详细输出测试信息"
    )

    # 工具选项
    parser.add_argument(
        "--list", "-l", action="store_true",
        help="列出所有测试文件但不运行"
    )
    parser.add_argument(
        "--validate", action="store_true",
        help="验证测试目录结构"
    )
    parser.add_argument(
        "--output", "-o", type=str,
        help="将测试结果保存到JSON文件"
    )

    return parser.parse_args()


def main():
    """主函数"""
    # 解析命令行参数
    args = parse_arguments()

    # 创建测试运行器
    runner = TestRunner()

    # 验证测试结构
    if args.validate:
        if not runner.validate_test_structure():
            sys.exit(1)
        return

    # 列出测试文件
    if args.list:
        test_structure = runner.discover_tests()
        print("\n📋 发现的测试文件:")
        print("="*60)
        total_files = 0

        category_names = {
            "unit": "单元测试",
            "integration": "集成测试",
            "performance": "性能测试"
        }

        for category, tests in test_structure.items():
            print(f"\n{category_names.get(category, category.upper())} ({len(tests)} 个文件):")
            if tests:
                for test in sorted(tests):
                    print(f"  📄 {test}")
                total_files += len(tests)
            else:
                print(f"  ⚠️  无测试文件")

        print(f"\n📊 总计: {total_files} 个测试文件")
        return

    # 运行测试
    success = True

    # 根据参数决定运行哪些测试
    if args.unit or args.integration or args.performance:
        # 运行特定类别
        categories = []
        if args.unit:
            categories.append("unit")
        if args.integration:
            categories.append("integration")
        if args.performance:
            categories.append("performance")

        results = {"categories": {}, "total": {"tests": 0, "passed": 0, "failed": 0, "skipped": 0, "duration": 0}}

        for category in categories:
            result = runner.run_category_tests(category, args.parallel, args.coverage)
            results["categories"][category] = result
            results["total"]["tests"] += result["count"]
            results["total"]["duration"] += result["duration"]

            if result["status"] == "pass":
                results["total"]["passed"] += result["count"]
            elif result["status"] == "skip":
                results["total"]["skipped"] += result["count"]
            else:
                results["total"]["failed"] += result["count"]
                success = False
    else:
        # 运行所有测试
        results = runner.run_all_tests(
            parallel=args.parallel,
            coverage=args.coverage,
            skip_slow=args.skip_slow
        )
        success = results["total"]["failed"] == 0

    # 打印总结
    all_passed = runner.print_summary(results)

    # 保存结果到文件
    if args.output:
        try:
            with open(args.output, "w", encoding="utf-8") as f:
                json.dump(results, f, indent=2, ensure_ascii=False)
            print(f"\n💾 测试结果已保存到: {args.output}")
        except Exception as e:
            print(f"\n❌ 保存测试结果失败: {e}")

    # 返回退出码
    sys.exit(0 if all_passed else 1)


if __name__ == "__main__":
    main()